//*********************************************************
//
//    Copyright (c) Microsoft. All rights reserved.
//    This code is licensed under the Apache License, Version 2.0.
//    THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
//    ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
//    IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
//    PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
//
//*********************************************************

namespace Microsoft.Research.ScientificWorkflow.TridentModel
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Windows;
    using System.Collections.ObjectModel;
    using TridentAPI;
    using Microsoft.Research.DataLayer;
    using System.ComponentModel;
    using Microsoft.Research.eResearch.Common.Linq;
    using System.Globalization;

    [Serializable()]
    public abstract class BaseModel : INotifyPropertyChanged
    {
        /// <summary>
        /// Label.
        /// </summary>
        private string label;

        /// <summary>
        /// Flag specifies if the model can be part of subsection or no.
        /// </summary>
        private bool allowInsertIntoSubsection = true;

        /// <summary>
        /// List of input parameters.
        /// </summary>
        private Collection<ParameterDescriptionModel> inputParams = new Collection<ParameterDescriptionModel>();

        /// <summary>
        /// List of output parameters.
        /// </summary>
        private Collection<ParameterDescriptionModel> outputParams = new Collection<ParameterDescriptionModel>();

        /// <summary>
        /// Associated assemblies.
        /// </summary>
        private Collection<AssemblyInfoModel> associatedAssemblies = new Collection<AssemblyInfoModel>();

        /// <summary>
        /// Visibility of the activity and it's parameters.
        /// </summary>
        private ActivityModelVisibility visibleOnUi = new ActivityModelVisibility(Visibility.Visible, Visibility.Visible);

        /// <summary>
        /// The delete behavior of the activity.
        /// </summary>
        private ActivityDeleteBehavior deleteBehavior = ActivityDeleteBehavior.DeleteSelf;

        /// <summary>
        /// Holds the Id of the latest object.
        /// </summary>
        private Guid latestEntityId;

        /// <summary>
        /// Flag indicating if update available.
        /// </summary>
        private bool updateAvailable;

        [field: NonSerialized]
        private BaseCompositeModel parentModel;

        /// <summary>
        /// Geta a human readable Name of the SubSection.
        /// </summary>
        public string Name
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets a value indicating whether this instance is preview.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if this instance is preview; otherwise, <c>false</c>.
        /// </value>
        public bool IsPreview { get; set; }

        /// <summary>
        /// Gets the description of Subsection.
        /// </summary>
        public virtual string Description
        {
            get;
            set;
        }

        /// <summary>
        /// Gets and sets a workflowName particularly relevant to a design tool working with this activity.
        /// This property will fall back to Name if no label was ever specified.
        /// </summary>
        public virtual string Label
        {
            get
            {
                if (string.IsNullOrEmpty(label))
                {
                    this.label = this.Name;
                }

                return this.label;
            }
            set
            {
                string assignedVal = value;
                if (!string.IsNullOrEmpty(assignedVal) && !string.IsNullOrEmpty(assignedVal.Trim()))
                {
                    this.label = assignedVal.Trim();
                }
            }
        }

        /// <summary>
        /// Indicates soft delete.
        /// </summary>
        public bool IsDeleted
        {
            get;
            set;
        }

        /// <summary>
        /// Flag to check if the activity is collapsed in the UI.
        /// </summary>
        public virtual bool IsCollapsed
        {
            get;
            set;
        }

        /// <summary>
        /// Gets the activity model of the subsection holder.
        /// </summary>
        public BaseCompositeModel ParentModel
        {
            get
            {
                return this.parentModel;
            }
            set
            {
                this.parentModel = value;
            }
        }

        /// <summary>
        /// Gets the Guid of the activity in the registry.
        /// </summary>
        public Guid Id
        {
            get;
            set;
        }

        /// <summary>
        /// Gets and sets the unique name of the activity in the workflow model pertains to.
        /// </summary>
        public string UniqueId
        {
            get;
            set;
        }

        /// <summary>
        /// Gets the models for the input parameters.
        /// </summary>
        public Collection<ParameterDescriptionModel> InputParameters
        {
            get
            {
                return this.inputParams;
            }
        }

        /// <summary>
        /// Gets the models for the required input parameters.
        /// </summary>
        public Collection<ParameterDescriptionModel> MandatoryInputParameters
        {
            get
            {
                return new Collection<ParameterDescriptionModel>((from param in this.InputParameters where param.IsMandatory select param).ToList());
            }
        }

        /// <summary>
        /// Gets the models for the required input parameters and inputs that are wrong.
        /// </summary>
        public Collection<ParameterDescriptionModel> MandatoryAndWrongInputParameters
        {
            get
            {
                return new Collection<ParameterDescriptionModel>(((from param in this.InputParameters where param.IsMandatory && param.IsNotMandatoryForWorkflow select param)
                    .Concat(this.WrongInputs)).Distinct().ToList());
            }
        }

        /// <summary>
        /// Gets the list of mandatory (and non-mandatory which have wrong values).
        /// </summary>
        public Collection<ParameterDescriptionModel> WrongInputs
        {
            get
            {
                return new Collection<ParameterDescriptionModel>(this.InputParameters.Where(new Func<ParameterDescriptionModel, bool>(this.IsWrong)).ToList());
            }
        }

        /// <summary>
        /// Gets the models for the output parameters.
        /// </summary>
        public Collection<ParameterDescriptionModel> OutputParameters
        {
            get
            {
                return this.outputParams;
            }
        }

        /// <summary>
        /// Gets or sets the visibility of the Bindings.
        /// </summary>
        public abstract Visibility CurrentParameterBindingVisibility
        {
            get;
            set;
        }

        /// <summary>
        /// Gets the Type of the activity that this model pertains to.
        /// </summary>
        public string ActivityClass
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the flag specifying if the model can be part of subsection or no.
        /// </summary>
        public bool AllowInsertIntoSubsection
        {
            get
            {
                return this.allowInsertIntoSubsection;
            }

            set
            {
                this.allowInsertIntoSubsection = value;
            }
        }

        /// <summary>
        /// Gets or sets the representation of the activity on the property pane.
        /// </summary>
        public virtual ActivityModelVisibility VisibleOnUI
        {
            get
            {
                return this.visibleOnUi;
            }

            set
            {
                this.visibleOnUi = value;
            }
        }

        /// <summary>
        /// The delete behavior of the activity.
        /// </summary>
        public ActivityDeleteBehavior DeleteBehavior
        {
            get { return deleteBehavior; }
            set { deleteBehavior = value; }
        }

        /// <summary>
        /// Gets or sets the latest entity id.
        /// </summary>
        /// <value>The latest entity id.</value>
        public virtual Guid LatestEntityId
        {
            get
            {
                return this.latestEntityId;
            }
            set
            {
                this.latestEntityId = value;
                this.RaisePropertyChanged("LatestEntityId");
            }
        }

        /// <summary>
        /// Gets or sets a value indicating whether new version available.
        /// </summary>
        /// <value><c>true</c> if new version available; otherwise, <c>false</c>.</value>
        public virtual bool UpdateAvailable
        {
            get
            {
                return this.updateAvailable;
            }

            set
            {
                this.updateAvailable = value;
                this.RaisePropertyChanged("UpdateAvailable");
            }
        }

        /// <summary>
        /// Gets true if the concrete implementation is a simple activity model.
        /// </summary>
        public abstract bool IsSimpleActivityModel { get; }

        /// <summary>
        /// Gets true if the concrete implementation is a composite activity model.
        /// </summary> 
        public abstract bool IsCompositeActivityModel { get; }

        /// <summary>
        /// Gets true if the concrete implementation is a subsection model.
        /// </summary> 
        public abstract bool IsSubsectionModel { get; }

        /// <summary>
        /// Gets true if the concrete implementation is a For model.
        /// </summary> 
        public abstract bool IsForActivityModel { get; }

        /// <summary>
        /// Gets true if the concrete implementation is a Replicator model.
        /// </summary> 
        public abstract bool IsReplicatorActivityModel { get; }

        /// <summary>
        /// Gets true if the concrete implementation is an ifElse model.
        /// </summary> 
        public abstract bool IsIfElseActivityModel { get; }

        /// <summary>
        /// Gets true if the concrete implementation is a simple blackbox model.
        /// </summary>
        public abstract bool IsSimpleBlackBoxModel { get; }

        /// <summary>
        /// Gets true if the concrete implementation is a composite blackbox model.
        /// </summary>
        public abstract bool IsCompositeBlackBoxModel { get; }

        public bool IsProvenanceMode { get; set; }

        /// <summary>
        /// Gets the index of the model.
        /// </summary>
        public virtual int IndexOfModel
        {
            get
            {
                int index = -1;
                if (this.ParentModel != null && this.ParentModel.Children.Contains(this))
                {
                    index = this.ParentModel.Children.IndexOf(this);
                }

                return index;
            }
        }

        /// <summary>
        /// Gets or sets the version.
        /// </summary>
        /// <value>The version.</value>
        public virtual int Version { get; set; }

        /// <summary>
        /// Gets a value indicating whether this instance is latest.
        /// </summary>
        /// <value><c>true</c> if this instance is latest; otherwise, <c>false</c>.</value>
        public bool IsLatest
        {
            get
            {
                return this.Id.Equals(this.LatestEntityId);
            }
        }

        /// <summary>
        /// Gets or sets the associated assemblies.
        /// </summary>
        /// <value>The associated assemblies.</value>
        public Collection<AssemblyInfoModel> AssociatedAssemblies
        {
            get
            {
                return this.associatedAssemblies;
            }
            set
            {
                this.associatedAssemblies = value;
            }
        }

        /// <summary>
        /// Get the abstract visitor for the operation handling.
        /// </summary>
        /// <param name="visitor">
        /// The base visitor.
        /// </param>
        public abstract void Accept(TridentBaseVisitor visitor);

        /// <summary>
        /// Check if the passed param has wrong values.
        /// In case the paramType is a custom type, it will always be returned as wrong.
        /// </summary>
        /// <param name="paramModel">
        /// The param to be checked.
        /// </param>
        /// <returns>
        /// If the value is correct returns true, else (or if  a custom type) returns false.
        /// </returns>
        private bool IsWrong(ParameterDescriptionModel paramModel)
        {
            return BaseModel.CheckValueValidity(paramModel);
        }

        /// <summary>
        /// Checks if the passed param value is a valid one.
        /// </summary>
        /// <param name="paramModel">
        /// Param Model to validate.
        /// </param>
        /// <returns>
        /// True if valid value, false otherwise.
        /// </returns>
        public static bool CheckValueValidity(ParameterDescriptionModel parameterModel)
        {
            bool needsFix = false;
            if (!parameterModel.IsDatabound)
            {
                if (null == parameterModel.Value || string.IsNullOrEmpty(parameterModel.Value.ToString()))
                {
                    needsFix = (parameterModel.IsMandatory && !parameterModel.IsMandatoryForWorkflow) ? true : false;
                }
                else
                {
                    try
                    {
                        TypeInitializer typeInit = TypeProviderHelper.FetchTypeInitializer(parameterModel.DataType, Runtime.WPF);
                        if (typeInit == null)
                        {
                            System.Type dataType = System.Type.GetType(parameterModel.DataType);
                            if (dataType != null)
                            {
                                object parsedValue = System.ComponentModel.TypeDescriptor.GetConverter(dataType).ConvertFromString(parameterModel.Value.ToString());
                                if (null == parsedValue)
                                {
                                    needsFix = true;
                                }
                            }
                        }
                        else
                        {
                            string deserializedVal = (parameterModel.Value != null) ? parameterModel.Value.ToString() : string.Empty;
                            BaseTypeInitializer baseTypeInit = TypeProviderHelper.LoadInitializer(typeInit, deserializedVal);
                            if (baseTypeInit != null)
                            {
                                if (parameterModel.IsMandatory)
                                {
                                    needsFix = !baseTypeInit.ContainsValidValue();
                                }
                                else
                                {
                                    needsFix = !(baseTypeInit.ContainsValidValue() || baseTypeInit.ContainsEmptyValue());
                                }
                            }
                        }
                    }
                    catch (Exception)
                    {
                        if (!(string.IsNullOrEmpty(parameterModel.Value.ToString()) && !parameterModel.IsMandatory))
                        {
                            needsFix = true;
                        }
                    }
                }
            }

            return needsFix;
        }

        /// <summary>
        /// Recurses through the model to fetch the workflow required parameters.
        /// </summary>
        /// <param name="baseModel">The model.</param>
        /// <param name="list">The list into which the parameters are to be populated.</param>
        internal static void GetWorkflowRequiredParameters(BaseModel baseModel, Collection<ParameterDescriptionModel> list)
        {
            // Retrieve parameters for the activity.
            if (baseModel.IsSimpleActivityModel || baseModel.IsCompositeActivityModel)
            {
                var mandatoryList = from param in baseModel.InputParameters
                                    where param.IsMandatoryForWorkflow
                                    select param;

                foreach (ParameterDescriptionModel requiredModel in mandatoryList)
                {
                    requiredModel.PropertyPaneDisplayName = string.Format(CultureInfo.InvariantCulture, "{0}.{1}", baseModel.Name, requiredModel.PropertyName);
                    list.Add(requiredModel);
                }
            }

            // Recurse through the children and get their info.
            if (baseModel.IsSubsectionModel || baseModel.IsCompositeActivityModel || baseModel.IsCompositeBlackBoxModel)
            {
                BaseCompositeModel compositeModel = baseModel as BaseCompositeModel;

                foreach (BaseModel childModel in compositeModel.Children)
                {
                    GetWorkflowRequiredParameters(childModel, list);
                }
            }
        }
        
        /// <summary>
        /// Returns a <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>.
        /// </summary>
        /// <returns>
        /// A <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>.
        /// </returns>
        public override string ToString()
        {
            return this.UniqueId;
        }
        
        /// <summary>
        /// Checks if update available.
        /// </summary>
        /// <param name="activity">The activity.</param>
        /// <returns></returns>
        public static Activity CheckIfUpdateAvailable(Activity activity)
        {
            Activity latestActivity = null;
            if (activity != null && !activity.IsLatestVersion())
            {
                latestActivity = activity.GetLatestActivity();
            }

            return latestActivity;
        }

        /// <summary>
        /// Populates the associated assemblies.
        /// </summary>
        /// <param name="activity">The activity.</param>
        public void PopulateAssociatedAssemblies(Activity activity)
        {
            if (activity.AssemblyPackages.Count > 0)
            {
                // Maintain the assembly information.
                activity.AssemblyPackages[0].AssemblyImages
                    .ForEach(assemblyImage => this.associatedAssemblies
                        .Add(new AssemblyInfoModel()
                        {
                            AssemblyFullName = assemblyImage.Filename,
                            AssemblyChecksum = assemblyImage.Checksum,
                            IsActivityDll = assemblyImage.IsActivityDll,
                            HolderModel = this
                        }));
            }
        }

        #region INotifyPropertyChanged Members

        /// <summary>
        /// Occurs when a property value changes.
        /// </summary>
        [field: NonSerialized]
        public event PropertyChangedEventHandler PropertyChanged;

        /// <summary>
        /// Raises the property changed.
        /// </summary>
        /// <param name="name">The name.</param>
        protected void RaisePropertyChanged(string name)
        {
            if (this.PropertyChanged != null)
            {
                this.PropertyChanged(this, new PropertyChangedEventArgs(name));
            }
        }

        #endregion
    }


    [Serializable()]
    public class AssemblyInfoModel
    {
        public bool IsActivityDll { get; set; }

        public string AssemblyFullName { get; set; }

        public string AssemblyChecksum { get; set; }

        public BaseModel HolderModel { get; set; }
    }

    // Custom comparer for the AssemblyInfoModel class.
    public class AssemblyInfoComparer : IEqualityComparer<AssemblyInfoModel>
    {
        // Products are equal if their names and product numbers are equal.
        public bool Equals(AssemblyInfoModel x, AssemblyInfoModel y)
        {

            // Check whether the compared objects reference the same data.
            if (Object.ReferenceEquals(x, y)) return true;

            // Check whether any of the compared objects is null.
            if (Object.ReferenceEquals(x, null) || Object.ReferenceEquals(y, null))
                return false;

            // Check whether the products' properties are equal.
            return x.AssemblyFullName.Equals(y.AssemblyFullName);
        }

        // If Equals() returns true for a pair of objects,
        // GetHashCode must return the same value for these objects.

        public int GetHashCode(AssemblyInfoModel obj)
        {
            // Check whether the object is null.
            if (Object.ReferenceEquals(obj, null)) return 0;

            // Get the hash code for the Name field if it is not null.
            return obj.AssemblyFullName.GetHashCode();
        }
    }


    // Custom comparer for the AssemblyInfoModel class.
    public class AssemblyInfoChecksumComparer : IEqualityComparer<AssemblyInfoModel>
    {
        // Products are equal if their names and product numbers are equal.
        public bool Equals(AssemblyInfoModel x, AssemblyInfoModel y)
        {
            // Check whether the compared objects reference the same data.
            if (Object.ReferenceEquals(x, y)) return true;

            // Check whether any of the compared objects is null.
            if (Object.ReferenceEquals(x, null) || Object.ReferenceEquals(y, null))
                return false;

            // Check whether the products' properties are equal.
            return x.AssemblyChecksum.Equals(y.AssemblyChecksum);
        }

        // If Equals() returns true for a pair of objects,
        // GetHashCode must return the same value for these objects.
        public int GetHashCode(AssemblyInfoModel obj)
        {
            // Check whether the object is null.
            if (Object.ReferenceEquals(obj, null)) return 0;

            // Get the hash code for the Name field if it is not null.
            return obj.AssemblyFullName.GetHashCode();
        }

    }

}

